Skip to content

feat(secrets): secret-vault abstraction + Scaleway Secret Manager adapter - #129

Merged
Pomdapis merged 5 commits into
mainfrom
feat/secrets-vault-abstraction
Jul 24, 2026
Merged

feat(secrets): secret-vault abstraction + Scaleway Secret Manager adapter#129
Pomdapis merged 5 commits into
mainfrom
feat/secrets-vault-abstraction

Conversation

@Pomdapis

Copy link
Copy Markdown
Contributor

What

New vertical Compendium.Abstractions.Secrets + in-tree adapter Compendium.Adapters.Scaleway.SecretManager (mirrors the git-server abstraction pattern, #126).

AbstractionISecretVault facade with two concern-scoped ports:

  • ISecretContainerService (create/get/list-by-prefix+tags/delete)
  • ISecretVersionService (append immutable version, access by explicit revision — no "latest" in the port, list, enable/disable kill-switch, destroy)
  • Neutral SecretVaultConnection/SecretVaultCredential (redacted ToString()), SecretMaterial (never stringifies), declarative capabilities + EnsureSupported, SecretVault.* errors on the Result pattern, NullSecretVault fail-fast stub.
  • Design intent: (secretId, revision) is a stable value reference — which revision is current is the caller's metadata, so history is trustworthy and rollback is a re-pointing of references, portable across providers.

Testing kit (Compendium.Testing/Secrets) — InMemorySecretVault full-fidelity fake + SecretVaultContractTests behavioral contract (~16 assertions families: monotonic revisions, immutability, kill-switch round-trip + idempotency, destroy reserves the revision number, prefix/tag listing, redaction). Swapping vault backends is safe iff the adapter passes this suite.

Scaleway adapter — raw HTTP v1beta1 (no .NET SDK exists), X-Auth-Token per call, snake_case STJ models, uniform error mapping (409→ConflictExists, 429→Throttled+Retry-After metadata, ambiguous access 4xx disambiguated via version metadata into VersionDisabled/VersionNotFound/SecretNotFound), idempotent enable/disable, 64 KiB fail-fast, exactly-once writes (caller dedups by hash). CAPABILITIES.md packed.

Why

First brick of the Nexus "Config & Secrets v2" program (hybrid per-key delivery, org→project→app→env→version scoping, audit + one-click rollback). Scaleway SM is the v1 backend; the port + contract kit + documented migration path (copy data, rewrite refs, zero resolver change) is what makes the backend swappable later (in-house vault adapter).

Tests

  • 34 abstraction tests (incl. InMemory contract subscription) + 16 WireMock adapter tests — all green.
  • Full solution test suite: green except 4 pre-existing environment-dependent integration failures (Kubernetes sandbox expecting a live-cluster behavior; unrelated to this purely additive change).

Coordination

  • No shared-file friction expected: additive projects + 2-line Compendium.sln + 1 ProjectReference in Compendium.Testing.csproj.
  • Consumed next by Nexus branch feat/config-secrets-v2 (needs the next preview tag on the train after merge).

https://claude.ai/code/session_01F9hzMWYy7nKKhf6B2XxznV

sacha added 2 commits July 24, 2026 16:26
… contract kit

Provider-agnostic secret-vault abstraction: ISecretVault facade with two
concern-scoped ports (ISecretContainerService, ISecretVersionService), a
neutral connection/credential model (redacted ToString), declarative
per-adapter capabilities with EnsureSupported, and SecretVault.* errors on
the Result pattern. Versions are immutable and addressed by explicit
revision — no "latest" in the port — so (secretId, revision) is a stable
value reference: history stays trustworthy and rollback is a re-pointing
of references. NullSecretVault fail-fast stub for unconfigured hosts.

Compendium.Testing ships InMemorySecretVault (full-fidelity fake:
monotonic revisions, enable/disable/destroy, path-prefix listing, tag
filters, call log) and SecretVaultContractTests, the behavioral contract
every adapter must pass — swapping vault backends is safe by construction.

34 new tests green; solution builds clean.

Claude-Session: https://claude.ai/code/session_01F9hzMWYy7nKKhf6B2XxznV
Concrete ISecretVault over the regional Secret Manager API. Raw-HTTP
executor (X-Auth-Token per call, no provider SDK), snake_case STJ models,
uniform SecretVault.* error mapping: 409→ConflictExists, 429→Throttled
with Retry-After metadata, quota heuristics, and disambiguation of the
provider's ambiguous 4xx on version access into the precise
VersionDisabled / VersionNotFound / SecretNotFound via a metadata read.
Enable/disable made idempotent (no-op transition reads as success);
64 KiB payloads fail fast before any network call; version writes are
sent exactly once (caller owns dedup — documented). Prefix listing and
tag filters are applied adapter-side over paged results, keeping the
neutral semantics uniform. Credential-free options: the IAM key travels
in SecretVaultConnection, so one adapter instance serves any tenancy.

CAPABILITIES.md documents the matrix (LargePayload=None 64KiB,
Tags=Partial key:value encoding). 16 WireMock tests green.

Claude-Session: https://claude.ai/code/session_01F9hzMWYy7nKKhf6B2XxznV
Comment on lines +42 to +51
foreach (var segment in segments)
{
if (string.IsNullOrWhiteSpace(segment) || segment.Length > 100 ||
!segment.All(c => char.IsLetterOrDigit(c) || c is '-' or '_' or '.'))
{
return Result.Failure<SecretScopePath>(Error.Validation(
$"{SecretVaultErrors.Prefix}.InvalidPathSegment",
$"Invalid path segment '{segment}': segments must be 1-100 characters of letters, digits, '-', '_' or '.'."));
}
}
sacha added 3 commits July 24, 2026 16:55
The unit suite keeps growing (git, geo, messaging, now secrets verticals:
+2 test projects in this branch); main runs already ranged 8-17.6m on the
self-hosted runner and this branch's run was canceled at the 20m cap with
every completed project green. 30m gives the suite honest headroom.

Claude-Session: https://claude.ai/code/session_01F9hzMWYy7nKKhf6B2XxznV
Raises Compendium.Abstractions.Secrets to 100% and the Scaleway adapter
to ~97% line coverage (the 90% aggregate gate failed at 89.26%): error
factory branches, SecretMaterial copy semantics, tag/path/status mapping
edges, DI wiring + capability matrix, destroy/quota/non-JSON/network
failure paths, tenancy-over-default resolution, unfiltered prefix
listing, pagination failure propagation.

The DI wiring test caught a real bug: TryAddEnumerable rejects a
factory-only descriptor (indistinguishable implementation type) — the
facade registration now uses the typed factory overload.

Claude-Session: https://claude.ai/code/session_01F9hzMWYy7nKKhf6B2XxznV
… Compendium.Testing.Tests

The CI coverage gate max-merges ONE cobertura report per assembly; the
Compendium.Testing assembly was measured through whichever single test
project exercised it most, losing the cross coverage (the git report sees
InMemoryGitServer but not InMemorySecretVault, and vice versa) — which is
what left the aggregate at 89.86% despite the new packages sitting at
98-100%. Testing.Tests now runs both contract kits against both fakes, so
one report covers the assembly's full surface (66.7% → 70.2% measured on
unique lines). The original per-vertical subscriptions stay where they
are; the duplication rationale is documented on the fixtures.

Claude-Session: https://claude.ai/code/session_01F9hzMWYy7nKKhf6B2XxznV
@Pomdapis
Pomdapis merged commit cb45891 into main Jul 24, 2026
5 checks passed
@Pomdapis
Pomdapis deleted the feat/secrets-vault-abstraction branch July 24, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant